﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace ZOIL.Framework.Presentation
{
    /// <summary>
    /// 
    /// </summary>
    public class ZComponentFrames : Panel, ISemanticZoom
    {
        #region dependency properties

        #region ItemsSource

        /// <summary>
        /// 
        /// </summary>
        public IEnumerable<FrameworkElement> ItemsSource
        {
            get { return (IEnumerable<FrameworkElement>)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable<FrameworkElement>), typeof(ZComponentFrames), new UIPropertyMetadata(null, OnItemsSourceChanged));

        private static void OnItemsSourceChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            (sender as ZComponentFrames).OnItemsSourceChangedInternal(e);
        }

        internal void OnItemsSourceChangedInternal(DependencyPropertyChangedEventArgs e)
        {
            if (e.OldValue != null)
            {
                var collection = e.OldValue as INotifyCollectionChanged;
                if (collection != null)
                    collection.CollectionChanged -= OnCollectionChanged;
            }


            if (e.NewValue != null)
            {
                var fes = e.NewValue as IEnumerable<FrameworkElement>;

                foreach (FrameworkElement fe in fes)
                    if (fe != null)
                        AddZComponentFrame(fe);

                var collection = e.NewValue as INotifyCollectionChanged;
                if (collection != null)
                    collection.CollectionChanged += OnCollectionChanged;
            }
        }

        private void AddZComponentFrame(FrameworkElement fe)
        {
            var widthNeeded = ZComponentFrame.GetWidthNeeded(fe);

            var newFrame = new ZComponentFrame
                               {
                                   WidthNeeded = widthNeeded
                               };

            var grid = new Grid
                           {
                               Width = Width,
                               Height = Height
                           };
            grid.Children.Add(fe);

            newFrame.Child = grid;
            Children.Add(newFrame);
        }

        private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (var o in e.NewItems)
                    {
                        var fe = o as FrameworkElement;
                        if (fe != null)
                            AddZComponentFrame(fe);
                    }
                    break;
                case NotifyCollectionChangedAction.Move:
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (var o in e.OldItems)
                    {
                        var fe = o as FrameworkElement;
                        if (fe != null)
                        {
                            var toBeRemoved = Children.OfType<ZComponentFrame>().Where(frame => (frame.Child as FrameworkElement) == fe).ToArray();
                            foreach (var frame in toBeRemoved)
                                Children.Remove(frame);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    break;
                case NotifyCollectionChangedAction.Reset:
                    break;
                default:
                    break;
            }
        }

        #endregion

        #region FrameTransition

        public AnimationTimeline FrameTransition
        {
            get { return (AnimationTimeline)GetValue(AnimationProperty); }
            set { SetValue(AnimationProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FrameTransition.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AnimationProperty =
            DependencyProperty.Register("FrameTransition", typeof(AnimationTimeline), typeof(ZComponentFrames), new PropertyMetadata(new DoubleAnimation(1.0, 0.0, TimeSpan.FromSeconds(1.0), FillBehavior.Stop), OnFrameTransitionChanged));

        private static void OnFrameTransitionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var target = (ZComponentFrames)d;
            var oldFrameTransition = e.OldValue as AnimationTimeline;
            var newFrameTransition = e.NewValue as AnimationTimeline;
            target.OnFrameTransitionChanged(oldFrameTransition, newFrameTransition);
        }

        protected virtual void OnFrameTransitionChanged(AnimationTimeline oldFrameTransition, AnimationTimeline newFrameTransition)
        {
            if (oldFrameTransition != null && !oldFrameTransition.IsFrozen)
                oldFrameTransition.Completed -= OnFrameTransitionOnCompleted;

            if (newFrameTransition != null)
                newFrameTransition.Completed += OnFrameTransitionOnCompleted;
        }

        #endregion

        #endregion

        #region private fields

        private ZComponentFrame _currentFrame;
        private ZComponentFrame _collapseFrame;	// hack

        private double? _lastRenderedWidth;
        public static bool IsLastRenderWidthCheck = true;

        #endregion

        #region public properties

        /// <summary>
        /// 
        /// </summary>
        public FrameworkElement CurrentRepresentation
        {
            get
            {
                if (_currentFrame != null)
                    return _currentFrame;
                else
                    return null;
            }
        }

        #endregion

        #region public methods

        public void Update(ZComponent parent)
        {
            var width = parent.RenderedSize.Width;
            if ((IsLastRenderWidthCheck && _lastRenderedWidth != null && width.Equals(_lastRenderedWidth.Value)))
                return;

            //UpdateOld(parent);
            UpdateNew(parent);

            _lastRenderedWidth = width;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="parent"></param>
        public void UpdateOld(ZComponent parent)
        {
            if (parent == null)
            {
                throw new ArgumentNullException("parent");
            }

            ZComponentFrame candidate = null;
            double candidateValue = 0.0;

            foreach (UIElement child in InternalChildren)
            {
                ZComponentFrame frame = child as ZComponentFrame;

#if DEBUG
                if (frame == null)
                    throw new InvalidOperationException("ZComponentFrames may only have ZComponentFrame as children.");
#endif

                if (parent.RenderedSize.Width >= frame.WidthNeeded
                    && candidateValue <= frame.WidthNeeded)
                {
                    candidate = frame;
                    candidateValue = frame.WidthNeeded;
                }
            }

            if (candidate != null
                && candidate != _currentFrame)
            {
                if (_collapseFrame != null)
                {
                    _collapseFrame.Hide();
                    _collapseFrame = null;
                }

                candidate.Show();

                if (_currentFrame != null)
                {
                    if (!parent.IsOffScreen)
                    {
                        DoubleAnimation fade = new DoubleAnimation(1.0, 0.0,
                        TimeSpan.FromSeconds(1.0), FillBehavior.Stop);
                        _collapseFrame = _currentFrame;

                        _currentFrame.BeginAnimation(UIElement.OpacityProperty, fade);
                        Panel.SetZIndex(_currentFrame, 1);
                        Panel.SetZIndex(candidate, 0);
                    }
                    else
                        _currentFrame.Hide();
                }

                _currentFrame = candidate;
            }
        }

        private void UpdateNew(ZComponent parent)
        {
            if (parent == null)
                throw new ArgumentNullException("parent");

            var availableWidth = parent.RenderedSize.Width;

            var frameCandidate = InternalChildren.OfType<ZComponentFrame>().Where(frame => frame.WidthNeeded <= availableWidth).OrderBy(frame => frame.WidthNeeded).Last();

            if (frameCandidate != null && frameCandidate != _currentFrame)
            {
                if (_collapseFrame != null)
                {
                    _collapseFrame.Hide();
                    _collapseFrame = null;
                }

                frameCandidate.Show();

                if (_currentFrame != null)
                {
                    if (!parent.IsOffScreen)
                    {
                        _collapseFrame = _currentFrame;

                        SetZIndex(_collapseFrame, 1);
                        SetZIndex(frameCandidate, 0);

                        // If the duration is set to 00:00:00 the completed event will not fire.
                        // Otherwise the collapse frame will not collapse.
                        if (FrameTransition != null && FrameTransition.Duration > new Duration(TimeSpan.FromMilliseconds(0)))
                            _collapseFrame.BeginAnimation(OpacityProperty, FrameTransition);
                        else
                            SwitchFrames();
                    }
                    else
                        _currentFrame.Hide();
                }

                _currentFrame = frameCandidate;
            }
        }

        #endregion

        #region overrides

        /// <summary>
        /// 
        /// </summary>
        /// <param name="availableSize"></param>
        /// <returns></returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            var idealSize = new Size(0, 0);

            foreach (UIElement child in InternalChildren)
            {

                if (child.Visibility != Visibility.Visible)
                    continue;

                child.Measure(availableSize);

                if (child.Visibility == Visibility.Visible)
                {
                    idealSize.Width = Math.Max(idealSize.Width, child.DesiredSize.Width);
                    idealSize.Height = Math.Max(idealSize.Height, child.DesiredSize.Height);
                }
            }

            if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height))
                return idealSize;

            return availableSize;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="finalSize"></param>
        /// <returns></returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            foreach (UIElement child in InternalChildren)
                if (child.Visibility == Visibility.Visible)
                    child.Arrange(new Rect(0, 0, child.DesiredSize.Width, child.DesiredSize.Height));

            return finalSize;
        }

        #endregion

        #region FrameTransition

        private void OnFrameTransitionOnCompleted(object sender, EventArgs e)
        {
            SwitchFrames();
        }

        #endregion

        #region private methods

        private void SwitchFrames()
        {
            if (_collapseFrame == null) return;

            _collapseFrame.Hide();
            _collapseFrame = null;
        }

        #endregion
    }
}
